package web

import (
	"fmt"
	"net/http"
	"reflect"

	"gitee.com/thubcc/rtlmmap"
	"github.com/gin-gonic/gin"
	"github.com/prometheus/common/log"
)

type RFConfig struct {
	FreqCenter int `json:"fc"`
	SampleRate int `json:"sr"`
	Gain       int `json:"gain"`
}

type Port struct {
	RFConfig
	dev       *rtlmmap.Device
	Manufact  string `json:"manufact"`
	Product   string `json:"product"`
	Serial    string `json:"serial"`
	Index     int    `json:"index"`
	Installed bool   `json:"installed"`
	Opened    bool   `json:"opened"`
	Active    bool   `json:"active"`
}

func (p *Port) open() (err error) {
	if p.Opened {
		err = fmt.Errorf("Device %d has already been opened", p.Index)
		return
	}
	p.dev = rtlmmap.NewDevice(p.Index)
	p.dev.Open()
	p.dev.SetSampleRate(p.SampleRate)
	p.dev.SetGain(p.Gain)
	p.dev.SetFrequency(p.FreqCenter)
	p.Opened = true
	return
}

func (p *Port) start() (err error) {
	if !p.Opened {
		err = fmt.Errorf("Device %d must be opened before starting", p.Index)
		return
	}
	if p.Installed {
		p.resume()
		err = fmt.Errorf("Device %d has already been installed, just resume", p.Index)
		return
	}
	p.dev.Start()
	p.Installed = true
	return
}

func (p *Port) resume() {
	p.Active = true
	p.dev.Resume()
}

func (p *Port) suspend() {
	p.Active = false
	p.dev.Suspend()
}

func (p *Port) setFc(fc int) {
	p.dev.SetFrequency(fc)
	p.FreqCenter = fc
}

func (p *Port) setGain(g int) {
	p.dev.SetGain(g)
	p.Gain = g
}

func (p *Port) Open(c *gin.Context) {
	sfc := c.Query("fc")
	if sfc != "" {
		p.FreqCenter = Str1int(sfc)
	}
	ssr := c.Query("sr")
	if ssr != "" {
		p.SampleRate = Str1int(ssr)
	}
	sg := c.Query("gain")
	if sg != "" {
		p.Gain = Str10int(sg)
	}
	c.JSON(http.StatusOK, Response{Data: nil, Error: p.open()})
}

func (p *Port) Suspend(c *gin.Context) {
	p.suspend()
	c.JSON(http.StatusOK, Response{Data: nil, Error: nil})
}

func (p *Port) Resume(c *gin.Context) {
	p.resume()
	c.JSON(http.StatusOK, Response{Data: nil, Error: nil})
}

func (p *Port) Start(c *gin.Context) {
	c.JSON(http.StatusOK, Response{Data: nil, Error: p.start()})
}

func (p *Port) SetFc(c *gin.Context) {
	sfc := c.Query("fc")
	if sfc != "" {
		p.FreqCenter = Str1int(sfc)
	}
	p.setFc(p.FreqCenter)
	c.JSON(http.StatusOK, Response{Data: nil, Error: nil})
}

func (p *Port) SetGain(c *gin.Context) {
	sg := c.Query("gain")
	if sg != "" {
		p.Gain = Str10int(sg)
	}
	p.setGain(p.Gain)
	c.JSON(http.StatusOK, Response{Data: nil, Error: nil})
}

var (
	GlobPorts = NewPorts(&DefaultRFConfig)
)

type Ports struct {
	devs   map[int]*Port
	config *RFConfig
}

func NewPorts(config *RFConfig) *Ports {
	ps := Ports{devs: make(map[int]*Port), config: config}
	ps.listDriver()
	return &ps
}

func (ps *Ports) GetPort(port int) (p *Port, err error) {
	var ok bool
	if p, ok = ps.devs[port]; !ok {
		err = fmt.Errorf("Cannot get Device %d", port)
	}
	return
}

func (ps *Ports) onePort(port int) (oe error) {
	manufact, product, serial, err := rtlmmap.GetDeviceString(port)
	if err != nil {
		oe = err
		return
	}
	if _, ok := ps.devs[port]; !ok {
		ps.devs[port] = &Port{
			dev:       nil,
			Manufact:  manufact,
			Product:   product,
			Serial:    serial,
			Index:     port,
			Opened:    false,
			Installed: false,
			Active:    false,
			RFConfig:  *ps.config,
		}
	}
	return
}

func (ps *Ports) listDriver() {
	c := rtlmmap.GetDeviceCount()
	for i := 0; i < c; i++ {
		if err := ps.onePort(i); err != nil {
			log.Info(err, "@", i)
		}
	}
	return
}

func (ps *Ports) PList(c *gin.Context) {
	c.JSON(http.StatusOK, Response{ps.devs, nil})
}

func (ps *Ports) checkPort(c *gin.Context) (p *Port, err error) {
	port := Str2int(c.Param("port"))
	var ok bool
	if p, ok = ps.devs[port]; !ok {
		err = fmt.Errorf("Can not find view %d", port)
		c.JSON(http.StatusInternalServerError, Response{Data: nil, Error: err})
	}
	return
}

func (ps *Ports) PortSpecificationAPI(name string) func(c *gin.Context) {
	return func(c *gin.Context) {
		if port, err := ps.checkPort(c); err == nil {
			getValue := reflect.ValueOf(port)
			methodValue := getValue.MethodByName(name)
			args := []reflect.Value{reflect.ValueOf(c)}
			methodValue.Call(args)
		} else {
			c.JSON(http.StatusOK, Response{nil, err})
		}
	}
}

func NewDevWithHandle(c *gin.Context) (v *Port) {
	port, _ := CommonParam(c)
	var err error
	v, err = GlobPorts.GetPort(port)
	if err != nil {
		c.JSON(http.StatusOK, Response{fmt.Sprintln(err), nil})
		return
	}
	v.Open(c)
	return
}
